#include "qe_log.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_driver.h"
#include "qe_serial.h"
#include "qe_earlycon.h"
#include "qe_interrupt.h"
#include "libfdt.h"



QELOG_DOMAIN("axi-uartlite");



#define AXI_UARTLITE_CR_TX_RESET                0x00000001  /* Reset transmitter FIFO */
#define AXI_UARTLITE_CR_RX_RESET                0x00000002  /* Reset receiver FIFO */
#define AXI_UARTLITE_CR_ENABLE_IRQ              0x00000010  /* Enable interrupt */

#define AXI_UARTLITE_SR_PARITY_ERROR            0x00000080
#define AXI_UARTLITE_SR_FRAME_ERROR             0x00000040
#define AXI_UARTLITE_SR_OVERRUN_ERROR           0x00000020
#define AXI_UARTLITE_SR_ENABLE_IRQ              0x00000010
#define AXI_UARTLITE_SR_TX_FIFO_FULL            0x00000008
#define AXI_UARTLITE_SR_TX_FIFO_EMPTY           0x00000004
#define AXI_UARTLITE_SR_RX_FIFO_FULL            0x00000002
#define AXI_UARTLITE_SR_RX_FIFO_VALID           0x00000001

#define AXI_UARTLITE_FIFO_DEPTH                 (16)

typedef struct 
{
    volatile qe_u32 rxfifo;
    volatile qe_u32 txfifo;
    volatile qe_u32 sr;
    volatile qe_u32 cr;
} axi_uartlite_reg;

typedef struct 
{
    qe_serial_dev serial;
    axi_uartlite_reg *reg;
    qe_intc_dev *intc;
    qe_uint irq;
    qe_uint clk;
    qe_uint baudrate;
    qe_uint buffer_size;
    qe_bool is_transfer_started;
} axi_uartlite_dev;



static void axi_uartlite_irq_disable(axi_uartlite_dev *dev)
{
    dev->reg->cr &= ~AXI_UARTLITE_CR_ENABLE_IRQ;
}

static void axi_uartlite_irq_enable(axi_uartlite_dev *dev)
{
    dev->reg->cr |= AXI_UARTLITE_CR_ENABLE_IRQ;
}

static void axi_uartlite_fifo_reset(axi_uartlite_dev *dev)
{
    dev->reg->cr |= AXI_UARTLITE_CR_TX_RESET | AXI_UARTLITE_CR_RX_RESET;
}

static void axi_uartlite_xfer(qe_serial_dev *serial, axi_uartlite_dev *dev)
{
    int i;
    char fifo[AXI_UARTLITE_FIFO_DEPTH];
    qe_uint wait;
    qe_uint read_once;
    wait = qe_ringbuffer_wait(serial->tx_ring);
    read_once = qe_min(wait, AXI_UARTLITE_FIFO_DEPTH);
    if (!read_once)
        return;
    qe_ringbuffer_read(serial->tx_ring, fifo, read_once);
    for (i=0; i<read_once; i++) {
    	dev->reg->txfifo = fifo[i];
    }
}

static qe_int axi_uartlite_transfer_start(qe_serial_dev *serial)
{
    qe_assert(serial);
    axi_uartlite_dev *dev = (axi_uartlite_dev *)serial->parent.priv;
    qe_assert(dev);

    if (dev->is_transfer_started) {
        qe_warning("transfer already started");
        return -(qe_err_common);
    }
    dev->is_transfer_started = qe_true;

    /* do one xfer */
    axi_uartlite_xfer(serial, dev);

    return 0;
}

static void axi_uartlite_tx_isr(qe_serial_dev *serial, axi_uartlite_dev *dev)
{
    int wait;

    if (!dev->is_transfer_started)
        return;

    wait = qe_ringbuffer_wait(serial->tx_ring);
    if (!wait) {
        dev->is_transfer_started = qe_false;
        if (serial->parent.tx_complete)
        	serial->parent.tx_complete(&serial->parent, QE_NULL);
    } else {
        axi_uartlite_xfer(serial, dev);
    }
}

static void axi_uartlite_rx_isr(qe_serial_dev *serial, axi_uartlite_dev *dev)
{
    char data;
    qe_uint wait;
    axi_uartlite_irq_disable(dev);

    while (dev->reg->sr & AXI_UARTLITE_SR_RX_FIFO_VALID) {
        data = dev->reg->rxfifo;
        qe_ringbuffer_write(serial->rx_ring, &data, 1);
    }

    axi_uartlite_irq_enable(dev);

    wait = qe_ringbuffer_wait(serial->rx_ring);
    qe_debug("wait:%d", wait);
    if (wait>0 && serial->parent.rx_indicate) {
        serial->parent.rx_indicate(&serial->parent, wait);
    }
}

static void axi_uartlite_isr(void *arg)
{
    qe_u32 sr;

    qe_serial_dev *serial = (qe_serial_dev *)arg;
    axi_uartlite_dev *dev = (axi_uartlite_dev *)serial->parent.priv;

    sr = dev->reg->sr;
    qe_debug("sr:%x", sr);

    if (sr & (AXI_UARTLITE_SR_RX_FIFO_FULL |
               AXI_UARTLITE_SR_RX_FIFO_VALID)) {
        axi_uartlite_rx_isr(serial, dev);
    }

    if (sr & AXI_UARTLITE_SR_TX_FIFO_EMPTY) {
        axi_uartlite_tx_isr(serial, dev);
    }
}

static int axi_uartlite_putc(qe_serial_dev *serial, char c)
{
    axi_uartlite_dev *dev = (axi_uartlite_dev *)serial->parent.priv;

    qe_assert(serial != QE_NULL);
	qe_assert(dev != QE_NULL);

    while (dev->reg->sr & AXI_UARTLITE_SR_TX_FIFO_FULL)
        ;

	dev->reg->txfifo = c;

    return 1;
}

static int axi_uartlite_getc(qe_serial_dev *serial)
{
	int c;
	axi_uartlite_dev *dev = (axi_uartlite_dev *)serial->parent.priv;

	qe_assert(serial != QE_NULL);
	qe_assert(dev != QE_NULL);

	c = -1;

    while (!(dev->reg->sr & AXI_UARTLITE_SR_RX_FIFO_VALID))
        ;
	
	c = dev->reg->rxfifo;
    qe_debug("data:0x%x", c);

	return c;
}

static qe_ret axi_uartlite_configure(qe_serial_dev *serial, qe_serial_configure *cfg)
{
	axi_uartlite_dev *uart = (axi_uartlite_dev *)serial->parent.priv;
	axi_uartlite_fifo_reset(uart);
	return qe_ok;
}

static qe_ret axi_uartlite_ioctl(qe_serial_dev *serial, int cmd, void *arg)
{
    axi_uartlite_dev *uart = (axi_uartlite_dev *)serial->parent.priv;

    switch (cmd) {

    case QE_DEV_IOC_CLR_INT:
        if (!uart->intc) {
            qe_error("%s no intc", serial->parent.name);
            return qe_err_notsupport;
        }
        axi_uartlite_irq_disable(uart);
        qe_intc_irq_disable(uart->intc, uart->irq);
        qe_info("%s disable irq:%d", serial->parent.name, uart->irq);
        break;

    case QE_DEV_IOC_SET_INT:
        if (!uart->intc) {
            qe_error("%s no intc", serial->parent.name);
            return qe_err_notsupport;
        }
        qe_intc_irq_enable(uart->intc, uart->irq);
        axi_uartlite_irq_enable(uart);
        qe_info("%s enable irq:%d", serial->parent.name, uart->irq);
        break;
    }

    return qe_ok;
}

static qe_uart_ops axi_uartlite_ops = {
	.configure      = axi_uartlite_configure,
	.ioctl          = axi_uartlite_ioctl,
    .getc           = axi_uartlite_getc,
    .putc           = axi_uartlite_putc,
    .transfer_start = axi_uartlite_transfer_start,
};

static void serial_dev_register(axi_uartlite_dev *dev, const char *name)
{
    qe_serial_configure config = QE_SERIAL_DEFCONFIG;

    config.baud_rate = dev->baudrate;
    config.bufsz = dev->buffer_size;
    qe_serial_dev *serial = &dev->serial;

    serial->ops = &axi_uartlite_ops;
    dev->is_transfer_started = qe_false;

    serial->config = config;
    qe_serial_register(serial, name,
        QE_DEV_F_INT_RX|QE_DEV_F_INT_TX, dev);

    qe_debug("%s register", name);
}

static qe_ret axi_uartlite_probe(const void *fdt, int offset)
{
    int len;
    qe_ret ret;
    const char *name;
    const char *name_intc;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_clk;
    const qe_u32 *prop_irq;
    const qe_u32 *prop_baud;
    const qe_u32 *prop_bufsz;
    qe_intc_dev *intc;
    axi_uartlite_dev *dev;

    name = fdt_get_name(fdt, offset, QE_NULL);

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

    prop_clk = (const qe_u32 *)fdt_getprop(fdt, offset, "clk", &len);
    if (!prop_clk) {
        qe_error("%s no clk prop", name);
        return qe_err_param;
    }

    prop_baud = (const qe_u32 *)fdt_getprop(fdt, offset, "baudrate", &len);
    if (!prop_baud) {
        qe_error("%s no baudrate prop", name);
        return qe_err_param;
    }

    name_intc = qe_of_get_interrupt_parent_name(fdt, offset);
    if (name_intc) {
        qe_debug("%s has interrupt parent %s", name_intc);
        intc = (qe_intc_dev *)qe_dev_find(name_intc);
        if (!intc) {
            qe_error("intc %s not found", name_intc);
            return qe_err_notfind;
        }
        prop_irq = (const qe_u32 *)fdt_getprop(fdt, offset, "interrupts", &len);
        if (!prop_irq) {
            qe_error("%s no interrupts prop", name);
            return qe_err_param;
        }
    }

    prop_bufsz = (const qe_u32 *)fdt_getprop(fdt, offset, "bufsz", &len);

    dev = qe_malloc(sizeof(axi_uartlite_dev));
    qe_assert(dev);

    dev->reg = (axi_uartlite_reg *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    dev->clk = fdt32_to_cpu(*prop_clk);
    dev->baudrate = fdt32_to_cpu(*prop_baud);

    if (prop_bufsz) {
        dev->buffer_size = fdt32_to_cpu(*prop_bufsz);
    } else {
        dev->buffer_size = 64;
    }

    if (prop_irq) {
        dev->intc = intc;
        dev->irq = fdt32_to_cpu(prop_irq[1]);
        ret = qe_intc_irq_register(dev->intc, dev->irq, axi_uartlite_isr, &dev->serial);
        if (ret != qe_ok) {
            qe_error("%s register irq %d err:%d", name, dev->irq, ret);
            qe_free(dev);
            return ret;
        }
        qe_debug("register irq:%d", dev->irq);
    }

    serial_dev_register(dev, name);

    return qe_ok;
}

static const qe_device_id uartlite_ids[] = {
    {.compatible = "xlnx,axi-uartlite",},
    {}
};

QE_DRIVER(axi_uartlite) = {
    .name = "uartlite",
    .of_match = uartlite_ids,
    .probe = axi_uartlite_probe,
};

QE_DRIVER_FORCE_EXPORT(axi_uartlite);

void uartlite_earlycon_init(const void *fdt, int offset)
{
}

void uartlite_earlycon_putc(qe_ubase base, char c)
{
    axi_uartlite_reg *reg = (axi_uartlite_reg *)base;
    
    while (reg->sr & AXI_UARTLITE_SR_TX_FIFO_FULL)
        ;

    reg->txfifo = c;
}

QE_EARLYCON(axi_uartlite) = {
	.compatible = "xlnx,axi-uartlite",
	.init = uartlite_earlycon_init,
	.putc = uartlite_earlycon_putc
};

QE_EARLYCON_FORCE_EXPORT(axi_uartlite);
